// boost graph wrapper

#ifndef NETWORK_HH_
#define NETWORK_HH_
#include "util.hh"
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/graph_traits.hpp>

#include <iostream>
#include <fstream>
#include <algorithm>
#include <numeric>


typedef boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS,
    boost::property<boost::vertex_index_t, int,
        boost::property<boost::vertex_color_t, int,
            boost::property<boost::vertex_degree_t, double> > >,
    boost::property<boost::edge_weight_t, double> > network_t;

template<typename Net>
void random_vertex_color(Net& G, const random_index_t& rIdx)
{
  typename boost::property_map<Net, boost::vertex_color_t>::type color =
      boost::get(boost::vertex_color, G);

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;

  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
  {
    boost::put(color, *vi, rIdx());
  }
}


template<typename Net, typename Map>
void import_vertex_color(const Map& cmap, Net& G)
{
  typename boost::property_map<Net, boost::vertex_color_t>::type color =
      boost::get(boost::vertex_color, G);

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;

  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
    {
      if( cmap.count(*vi) > 0 )
	boost::put(color, *vi, cmap.at(*vi));
    }
}

template<typename Net, typename Map>
void export_vertex_color(const Net&G, Map& cmap)
{
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;

  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
    {
      cmap[*vi] = boost::get(color, *vi);
    }
}

// construct vertex -> cluster degree-graph
template<typename Net>
void make_color_degree(typename Net::vertex_descriptor v, const Net& G,
    std::vector<double>& degree, int& kMin, int& kMax)
{
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);

  typename boost::property_map<Net, boost::edge_weight_t>::const_type weight =
      boost::get(boost::edge_weight, G);

  typename boost::graph_traits<Net>::out_edge_iterator ei, eEnd;

  kMin = degree.size();
  kMax = -1;

  for (boost::tie(ei, eEnd) = boost::out_edges(v, G); ei != eEnd; ++ei)
  {
    int k = boost::get(color, boost::target(*ei, G));

    degree[k] += get(weight, *ei);
    kMin = std::min(kMin, k);
    kMax = std::max(kMax, k);
  }
}

bool has_neighbor(const int kMin, const int kMax)
{
  return kMin <= kMax;
}

// zeroing stuffs
template<typename Net>
void erase_color_degree(typename Net::vertex_descriptor v, const Net& G,
    std::vector<double>& degree)
{

  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);

  typename boost::property_map<Net, boost::edge_weight_t>::const_type weight =
      boost::get(boost::edge_weight, G);

  typename boost::graph_traits<Net>::out_edge_iterator ei, eEnd;

  for (boost::tie(ei, eEnd) = boost::out_edges(v, G); ei != eEnd; ++ei)
  {
    int k = boost::get(color, boost::target(*ei, G));
    degree[k] = 0.;
  }

}

// color-specific size
template<typename Net>
void make_color_size(const Net& G, std::vector<double>& sz)
{
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);

  std::fill(sz.begin(), sz.end(), 0.);

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;
  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
    sz[boost::get(color, *vi)]++;
}

// color-specific volume
template<typename Net>
void make_color_volume(const Net& G, std::vector<double>& vol)
{
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);
  typename boost::property_map<Net, boost::vertex_degree_t>::const_type degree =
      boost::get(boost::vertex_degree, G);

  std::fill(vol.begin(), vol.end(), 0.);

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;
  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
    vol[boost::get(color, *vi)] += boost::get(degree, *vi);
}

// read a network from file,
// which contains a list of u,v,w
template<typename Net>
bool read_network(const char* file, Net& G)
{
  int u, v;
  double w;

  typename Net::edge_descriptor e;
  typename boost::property_map<Net, boost::edge_weight_t>::type weight = get(
      boost::edge_weight, G);

  typename boost::property_map<Net, boost::vertex_degree_t>::type degree = get(
      boost::vertex_degree, G);

  bool ok;
  int max_u = 0;
  {
    std::ifstream ifs(file, std::ios::in);
    while(ifs >> u >> v >> w)
      {
	max_u = u > max_u ? u : max_u;
	max_u = v > max_u ? v : max_u;
      }
    ifs.close();
  }

  G.clear();

  // add vertices  
  for(int j=0; j<=max_u; ++j)
    boost::add_vertex(G);

  // read file stream & add edges
  {
    std::ifstream ifs(file, std::ios::in);
    while (ifs >> u >> v >> w)
      {
	if( u < 0 || v < 0 ) continue;
	if( u == v ) continue;

	boost::tie(e, ok) = boost::edge(u,v,G);
	if (!ok)
	  {
	    boost::tie(e, ok) = boost::add_edge(u,v,G);
	    boost::put(weight, e, w);
	  }
      }
    ifs.close();  
  }

  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;
  typename boost::graph_traits<Net>::out_edge_iterator ei, eEnd;
  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
  {
    double d = 0.;
    for (boost::tie(ei, eEnd) = boost::out_edges(*vi, G); ei != eEnd; ++ei)
      d += boost::get(weight, *ei);

    boost::put(degree, *vi, d);
  }

//   for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
//   {
//     std::cerr << *vi;
//     for (boost::tie(ei, eEnd) = boost::out_edges(*vi, G); ei != eEnd; ++ei)
//       std::cerr << " (" << boost::source(*ei, G) << "," << boost::target(*ei, G) << ") ";
//     std::cerr << std::endl;
//   }

  typename boost::graph_traits<Net>::edges_size_type m = boost::num_edges(G);

  return (m > 0);
}

////////////////////////////////////////////////////////////////
// output network vertex colors
template<typename Net>
void write_color(const Net& G, const char* file)
{
  std::ofstream ofs(file, std::ios::out);
  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);
  typename boost::property_map<Net, boost::vertex_degree_t>::const_type degree =
      boost::get(boost::vertex_degree, G);

  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
  {
    if (boost::get(degree, *vi) > 0)
      ofs << *vi << "\t" << boost::get(color, *vi) << "\n";
  }
  ofs.close();
}

////////////////////////////////////////////////////////////////
// dump network
template<typename Net>
void dump_network(const Net & G)
{
  typename boost::graph_traits<Net>::out_edge_iterator ei, eEnd;
  typename boost::graph_traits<Net>::vertex_iterator vi, vEnd;
  typename boost::property_map<Net, boost::edge_weight_t>::const_type weight =
      boost::get(boost::edge_weight, G);
  typename boost::property_map<Net, boost::vertex_color_t>::const_type color =
      boost::get(boost::vertex_color, G);

  for (boost::tie(vi, vEnd) = boost::vertices(G); vi != vEnd; ++vi)
  {
    std::cerr << *vi << " (" << boost::get(color, *vi) << ") :";
    for (boost::tie(ei, eEnd) = boost::out_edges(*vi, G); ei != eEnd; ++ei)
    {
      std::cerr << "\t" << boost::target(*ei, G) << " [" << get(weight, *ei)
          << "]";
    }
    std::cerr << std::endl;
  }
}

#endif /* NETWORK_HH_ */
